package com.etone.smartAudit.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.etone.smartAudit.config.BNSConfig;
import com.etone.smartAudit.error.CustomParameterizedException;
import com.etone.smartAudit.utils.AESUtils;
import com.etone.smartAudit.vo.bns.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/***
 * 对接移动BSN区块链服务类
 */
@Slf4j
@Service
public class BSNService {


    @Autowired
    private BNSConfig config;

    @Autowired
    private RestTemplate restTemplate;

    @Value("${bns.host:http://121.15.167.229:8081}")
    private String hostUrl;

    @Autowired
    private RedisUtil redisUtil;

    private final String BNS_KEY = "bns:26935c243d007";


    @Value("${bns.aeskey:zqqm3sncCYry01Au}")
    private String aesKey;


    /***
     * 服务鉴权
     * 接口描述 ：企业用户数据上链之前，必须调用该鉴权接口进行身份认证，
     * 认证通过后存证服务器端会分配一个身份令牌：token，该token有效期2小时且不续期，如token过期后，
     * 需调用该接口获取新的token。注：B端业务服务器的IP需提前添加到存证服务端的白名单内。
     * 正式环境：https://apibaas.cmpassport.com/IDAuthChain/user/corpAuth
     * 测试环境：http://121.15.167.229:8081/IDAuthChain/user/corpAuth
     * header参数名称	参数说明	是否为必填	参数类型
     * corpId	企业ID	是	String
     * appId	应用唯一编号	是	String
     * cguid	请求事务ID，需保持唯一，18位：毫秒级时间戳+5位随机数	是	String
     * mac	数据签名	是	String
     */
    public String corpAuth() {
        String url = hostUrl + "/IDAuthChain/user/corpAuth";
        BNSConfig authConfig = config.initCorpAuth();
        JSONObject param = JSON.parseObject(JSON.toJSONString(authConfig));
        //移除多余的字段
        param.remove("token");
        param.remove("privateKey");
        HttpHeaders httpHeaders = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
        httpHeaders.setContentType(type);
        HttpEntity<JSONObject> entity = new HttpEntity<>(param, httpHeaders);
        ResponseEntity<BNSResult<CorpAuthVo>> response = restTemplate.exchange(url, HttpMethod.POST, entity, new ParameterizedTypeReference<BNSResult<CorpAuthVo>>() {
        });

        //储存到redis TODO
        if (response.getStatusCode() == HttpStatus.OK) {
            //请求成功
            BNSResult<CorpAuthVo> result = response.getBody();
            if (BNSResult.SUCCESS_CODE.equals(result.getCode())) {
                CorpAuthVo vo = result.getData();
                //缓存token到redis  有效期为2个小时，此处缓存1小时50分钟
                redisUtil.setEx(BNS_KEY, vo.getToken(), 2 * 60 - 10, TimeUnit.MINUTES);
                return vo.getToken();
            } else {
                log.error("获取服务端token失败" + result.getMsg());
                throw new CustomParameterizedException("获取服务端token失败-result:" + result.getMsg());
            }
        } else {
            log.error("获取服务端token失败");
            throw new CustomParameterizedException("获取服务端token失败");
        }
    }

    /***
     * 获取服务端验证token
     * @return
     */
    private String getToken() {
        int flag = 1;
        String token = redisUtil.get(BNS_KEY);
        if (StrUtil.isEmpty(token)) {
            //说明已经过期重新获取token
            corpAuth();
            token = redisUtil.get(BNS_KEY);
        }

        if (StrUtil.isEmpty(token)) {
            throw new CustomParameterizedException("获取不到区块链验证信息，请稍后再试!");
        }
        return token;
    }

    /***
     * 能力鉴权（原名为接口鉴权，业务接口）
     * 接口描述 ：B端调用方鉴权，B端用户需上报appid、token等参数给服务端，
     * 由存证服务端校验调用方能力配置、可用性、余量，并返回响应编码给调用方。
     * 正式环境：https://apibaas.cmpassport.com/IDAuthChain/user/capacity
     * 测试环境：http://121.15.167.229:8081/IDAuthChain/user/capacity
     */
    public CapacityVO capacity() {
        String url = hostUrl + "/IDAuthChain/user/capacity";
        JSONObject param = getBaseParam();
        HttpHeaders httpHeaders = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
        httpHeaders.setContentType(type);


        HttpEntity<JSONObject> entity = new HttpEntity<>(param, httpHeaders);
        ResponseEntity<BNSResult<CapacityVO>> response = restTemplate.exchange(url, HttpMethod.POST, entity, new ParameterizedTypeReference<BNSResult<CapacityVO>>() {
        });
        if (response.getStatusCode() == HttpStatus.OK) {
            //请求成功
            BNSResult<CapacityVO> result = response.getBody();
            if (BNSResult.SUCCESS_CODE.equals(result.getCode())) {
                CapacityVO vo = result.getData();
                //缓存token到redis
                return vo;
            } else {
                log.error("获取能力鉴权失败" + result.getMsg());
                throw new CustomParameterizedException("获取能力鉴权失败-result:" + result.getMsg());
            }
        } else {
            log.error("获取能力鉴权失败");
            throw new CustomParameterizedException("获取能力鉴权失败");
        }

    }

    /***
     * 文件存证上链接口（业务接口）
     * 单文件存证上链服务接口，异步上链，暂不支持多文件。
     * 测试环境地址：http://121.15.167.229:8081/IDAuthChain/chaincode/fileSetChain
     * 现网环境地址：https://apibaas.cmpassport.com/IDAuthChain/user/fileSetChain
     * {
     * 	"corpId": "10001",
     * 	"appId": "10000001",
     * 	"cguid": "158563151456300001",
     * 	"token": "OEU2ODZENjNBMUFCQTBDNDJD",
     * 	"userFile": "审计系统文件202009120001.doc",
     * 	"req": "asyn",
     * 	"abilityExt": "",
     * 	"transientData": {
     * 		"userInfo": "MEQCID27XE05k2gN71s2R94CfWZ79L6BG7daN3uDNvzBnk
     * 		4 IAiACR / 05 PKJMDAHpOurMTjC1KGiHeeZKhmdARU47CIUelg ",
     * 		"userFileHash": "MEQCID27XE05k2gN71s2R94CfWZ79L6BG7daN3u",
     * 		"processinfo": [
     * 			"MEQCID27XE05k2gN71s2R94CfWZ79L",
     * 			"pOurMTjC1KGiHeeZKh7XE05khmdARA",
     * 			"jC1KGiHeeZKh4CfWZ79L6BmdARU47CIUelg"
     * 		],
     * 		"content": "MEQCID27XE05k2gN71s2R94CfWZ79L6BG7daN3uDNvzBnk
     * 		4 IAiACR / 05 PKJMDAHpOurMTjC1KGiHeeZKhmdARU47CIUelg == ",
     * 		"mac": "MEQCID27XE05k2gN71s2R94CfWZ79L6BG7daN3uDNvzBnk
     * 		4 IAiACR / 05 PKJMDAHpOurMTjC1KGiHeeZKhmdARU47CIUelg == "
     *        }
     * }
     *
     * UserInfo=AES((phonenumber=xxxx&cardId=xxxx&userName=xxx&type=x&userTime=xxxx）,Appkey前16位)
     */
    public SetChainVO fileSetChain(SetChainDTO dto) {
        String url = hostUrl + "/IDAuthChain/chaincode/setChain";
        JSONObject param = getBaseParam();
        param.remove("mac");

        param.put("req", "asyn");
        //扩展信息字段，具体内容规范由业务与存证平台商定
        JSONObject transientData = new JSONObject();
        StringBuilder userSb = new StringBuilder();
//        1、将phonenumber=xxxx&cardId=xxxx&userName=xxx&type=x&userTime=xxxx&result=1的值拼接成字符串A，拼接时不需要插入“+”号；
        userSb.append("phonenumber=").append(dto.getPhone());
        userSb.append("&cardId=").append(dto.getCardId());
        userSb.append("&userName=").append(dto.getUserName());
        userSb.append("&type=").append(2);
        userSb.append("&userTime=").append(DateUtil.formatDateTime(new Date()));
        userSb.append("&result=").append(1);
        String usesrInfoStr = getAesValue(userSb.toString());
        transientData.put("userInfo", usesrInfoStr);
//        transientData.put("userFileHash", fileHash);
//        用户id|ip|001|时间戳|数据hash 不填留空
        JSONArray processinfo = new JSONArray();
        String processinfoStr = getAesValue(String.format("%s||0001|%s|%s", dto.getUid(), System.currentTimeMillis(), dto.getHash()));
        processinfo.add(processinfoStr);
        transientData.put("processInfos", processinfo);
        String content = getAesValue(dto.getContent());
        transientData.put("content", content);
        StringBuilder sb = new StringBuilder();
        sb.append(usesrInfoStr).append(processinfoStr)
                .append(content)
                .append(param.getString("token"));
        transientData.put("mac", config.sign(sb.toString()));
        transientData.put("dataExt", "");

        param.put("transientData", transientData);
        param.put("abilityExt", "");
        log.info(param.toJSONString());
        HttpHeaders httpHeaders = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
        httpHeaders.setContentType(type);
        HttpEntity<JSONObject> entity = new HttpEntity<>(param, httpHeaders);
        ResponseEntity<BNSResult<SetChainVO>> response = restTemplate.exchange(url, HttpMethod.POST, entity, new ParameterizedTypeReference<BNSResult<SetChainVO>>() {
        });
        if (response.getStatusCode() == HttpStatus.OK) {
            //请求成功
            BNSResult<SetChainVO> result = response.getBody();
            if (BNSResult.SUCCESS_CODE.equals(result.getCode())) {
                SetChainVO vo = result.getData();
                return vo;
            } else if ("110103".equals(result.getCode())) {
                corpAuth();
                //重新获取一次
            } else {
                log.error("文件上链失败" + result.getMsg());
                throw new CustomParameterizedException("文件上链失败-result:" + result.getMsg());
            }
        } else {
            log.error("文件上链失败");
            throw new CustomParameterizedException("文件上链失败");
        }
        return null;
    }


    /***
     * post  requestbody请求
     * @param param
     * @param url
     */
    private <T> void post(JSONObject param, String url, HandResult<T> hand) {
        HttpHeaders httpHeaders = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
        httpHeaders.setContentType(type);

        HttpEntity<JSONObject> entity = new HttpEntity<>(param, httpHeaders);
        ResponseEntity<BNSResult<T>> response = restTemplate.exchange(url, HttpMethod.POST, entity, new ParameterizedTypeReference<BNSResult<T>>() {
        });
        if (response.getStatusCode() == HttpStatus.OK) {
            //请求成功
            BNSResult<T> result = response.getBody();
            if (BNSResult.SUCCESS_CODE.equals(result.getCode())) {
                hand.handResult(result.getData());
            } else if ("110103".equals(result.getCode())) {
                //权限不足可能token 过期，重新获取token
                corpAuth();
            } else {
                log.error("请求失败" + result.getMsg());
                throw new CustomParameterizedException("获取能力鉴权失败-result:" + result.getMsg());
            }
        } else {
            log.error("获取能力鉴权失败");
            throw new CustomParameterizedException("获取能力鉴权失败");
        }

    }

    /***
     * 使用aes加密
     * @return
     */
    private String getAesValue(String content) {

        return AESUtils.encryptWithFunc(content, aesKey);
    }

    /***
     * 获取基数param
     * @return
     */
    private JSONObject getBaseParam() {
        BNSConfig authConfig = config.initCapacity(getToken());
        JSONObject param = JSON.parseObject(JSON.toJSONString(authConfig));
        param.remove("privateKey");
        return param;
    }


    /***
     * 存证查询接口（业务接口）
     * 接口描述 ：存证数据查询接口
     * 正式环境：https://apibaas.cmpassport.com/IDAuthChain/chaincode/queryChain
     * 测试环境：http://121.15.167.229:8081/IDAuthChain/chaincode/queryChain
     * 调用方式：POST+JSON
     */
    public BlockVO queryChain(String hash) {
        String url = hostUrl + "/IDAuthChain/chaincode/queryChain";
        JSONObject param = getBaseParam();
        param.remove("mac");
        JSONObject transientData = new JSONObject();
        transientData.put("hash", hash);
        param.put("transientData", transientData);
        HttpHeaders httpHeaders = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
        httpHeaders.setContentType(type);

        HttpEntity<JSONObject> entity = new HttpEntity<>(param, httpHeaders);
        log.info(param.toJSONString());
        ResponseEntity<BNSResult<BlockVO>> response = restTemplate.exchange(url, HttpMethod.POST, entity, new ParameterizedTypeReference<BNSResult<BlockVO>>() {
        });
        if (response.getStatusCode() == HttpStatus.OK) {
            //请求成功
            BNSResult<BlockVO> result = response.getBody();
            if (BNSResult.SUCCESS_CODE.equals(result.getCode())) {
//                hand.handResult(result.getData());
                return result.getData();
            } else {
                log.error("请求失败" + result.getMsg());
                throw new CustomParameterizedException("获取能力鉴权失败-result:" + result.getMsg());
            }
        } else {
            log.error("获取能力鉴权失败");
            throw new CustomParameterizedException("获取能力鉴权失败");
        }
    }


    /**
     * 执行回调
     *
     * @param <T>
     */
    public interface HandResult<T> {
        void handResult(T t);
    }


    /**
     * 获取文件的md5值
     *
     * @return
     */
    public String getFileMd5(String path) {
        try {
            InputStream inputStream = new FileInputStream(path);
            String md5 = DigestUtil.md5Hex(inputStream);
            return md5;
        } catch (FileNotFoundException e) {
            log.error("获取文件md5值错误", e);
        }
        return null;
    }

    public static void main(String[] args) {
        String content = "ewfewfewfewfewfew";
//        String key = "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCBKH4xbsrZ//1NnZAE9zOdfL3ii4YuEMHBEJcIogVeBfQ==";
        //随机生成密钥
        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();
        String tt = "zqqm3sncCYry01Au";
//        byte[] key = getSecretKey(tt).getEncoded();
        System.out.println(Base64.encodeBase64String(key));
        //使用AES加密
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
        //        加密
        byte[] encrypt = aes.encrypt(content);
        //解密
        byte[] decrypt = aes.decrypt(encrypt);
        //加密为16进制表示
        String encryptHex = aes.encryptHex(content);
        //解密为字符串
        String decryptStr = aes.decryptStr(encryptHex, CharsetUtil.CHARSET_UTF_8);
        System.out.println(encryptHex);
        System.out.println(decryptStr);
    }
}
